﻿using Dapper;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Micro.Extensions.Utilities;

namespace Micro.Extension.Data
{
    public static class DapperExtension
    {
        private static readonly ConcurrentDictionary<Type, TableDescribe> TableDescribes 
            = new ConcurrentDictionary<Type, TableDescribe>();
        public static TableDescribe GetTableInfo(Type tableType)
        {

            if (!TableDescribes.TryGetValue(tableType, out var td))
            {
                td = new TableDescribe
                {
                    TableName = tableType.Name.CamelNameToUnderLine()
                };
                foreach (var prop in tableType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.GetGetMethod(false) != null))
                {
                    //忽略属性 不存在表里
                    var attr = prop.GetCustomAttribute<IgnorePropertyAttribute>(true);
                    if (attr == null || !attr.Value)
                    {
                        var isKey = string.Equals(prop.Name, "id", StringComparison.CurrentCultureIgnoreCase);
                        td.Props.Add(new PropDescribe { CamelName = prop.Name, MapToColName = prop.Name.CamelNameToUnderLine(), IsKey = isKey });
                    }
                }
                TableDescribes[tableType] = td;
            }
            return td;
        }

        /// <summary>
        /// 查询
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dbConnection"></param>
        /// <param name="id"></param>
        /// <param name="transaction"></param>
        /// <param name="commandTimeout"></param>
        /// <returns></returns>
        public static async Task<T> GetAsync<T>(this IDbConnection dbConnection, object id, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var td = GetTableInfo(typeof(T));
            var cols = string.Join(",", td.Props.Select(x => x.MapToColName));
            var keyProp = td.GetKeyProp();

            var sql = $"select {cols} from {td.TableName} where {keyProp.MapToColName}=@id";
            return await dbConnection.QueryFirstOrDefaultAsync<T>(sql, new { id }, transaction, commandTimeout);
        }

        //新增
        public static async Task<int> InsertAsync<T>(this IDbConnection dbConnection, T entity, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var td = GetTableInfo(typeof(T));
            var cols = string.Join(",", td.Props.Select(x => x.MapToColName));
            var colsParams = string.Join(",", td.Props.Select(p => "@" + p.CamelName));

            var sql = $"insert into {td.TableName} ({cols}) values ({colsParams})";
            return await dbConnection.ExecuteAsync(sql, entity, transaction, commandTimeout);
        }

        //新增
        public static async Task<int> BatchInsertAsync<T>(this IDbConnection dbConnection, IEnumerable<T> entities, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var td = GetTableInfo(typeof(T));
            var cols = string.Join(",", td.Props.Select(x => x.MapToColName));
            var colsParams = string.Join(",", td.Props.Select(p => "@" + p.CamelName));

            var sql = $"insert into {td.TableName} ({cols}) values ({colsParams})";
            return await dbConnection.ExecuteAsync(sql, entities, transaction, commandTimeout);
        }


        //修改
        public static async Task<int> UpdateAsync<T>(this IDbConnection dbConnection, T entity, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var td = GetTableInfo(typeof(T));

            var keyProp = td.GetKeyProp();
            var setSql = string.Join(",", td.Props.Where(x => !x.IsKey).Select(p => $"{p.MapToColName}=@{p.CamelName}"));
            var sql = $"update {td.TableName} set {setSql} where {keyProp.MapToColName}=@{keyProp.CamelName}";
            return await dbConnection.ExecuteAsync(sql, entity, transaction, commandTimeout);
        }

        //删除
        public static async Task<int> DeleteAsync<T>(this IDbConnection dbConnection, T entity, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var td = GetTableInfo(typeof(T));

            var keyProp = td.GetKeyProp();
            var sql = $"delete from {td.TableName} where {keyProp.MapToColName}=@{keyProp.CamelName}";
            return await dbConnection.ExecuteAsync(sql, entity, transaction, commandTimeout);
        }

        //删除
        public static async Task<int> DeleteAsync<T>(this IDbConnection dbConnection, object id, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var td = GetTableInfo(typeof(T));

            var keyProp = td.GetKeyProp();
            var sql = $"delete from {td.TableName} where {keyProp.MapToColName}=@id";
            return await dbConnection.ExecuteAsync(sql, new { id }, transaction, commandTimeout);
        }

        /// <summary>
        /// 表描述信息
        /// </summary>
        public class TableDescribe
        {
            private PropDescribe _keyProp;
            public PropDescribe GetKeyProp()
            {
                return _keyProp ??= Props.First(x => x.IsKey);
            }

            //全部的属性
            public List<PropDescribe> Props { get; set; }
            //表面
            public string TableName { get; set; }
            public TableDescribe()
            {
                Props = new List<PropDescribe>();
            }
        }

        /// <summary>
        /// 属性映射关心
        /// </summary>
        public class PropDescribe
        {
            /// <summary>
            /// 实体对象属性名
            /// </summary>
            public string CamelName { get; set; }
            /// <summary>
            /// 映射表列名
            /// </summary>
            public string MapToColName { get; set; }
            /// <summary>
            /// 是否主键
            /// </summary>
            public bool IsKey { get; set; }
        }


    }
}
